package com.keruyun.portal.whitelist.gateway.lb;

import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <p>
 * Description: com.keruyun.portal.whitelist.gateway.lb
 * </p>
 * date：2018/7/5
 * email:crowhyc@163.com
 *
 * @author Dean.Hwang
 */
public class PortalRoundRobinRule {
    private AtomicInteger nextServerCyclicCounter;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public PortalRoundRobinRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public Server choose(List<Server> servers) {
        if (CollectionUtils.isEmpty(servers)) {
            log.warn("No up servers available ");
            return null;
        }
        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            int nextServerIndex = incrementAndGetModulo(servers.size());
            server = servers.get(nextServerIndex);
            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }
            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries ");
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (; ; ) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
}
